package com.kuaiyin.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 * 支持：String、Hash、List、Set、ZSet 类型
 * 可动态指定host、port、dbIndex及超时时间
 */
@Slf4j
@Component
public class RedisUtils {

    // 默认配置（从application.yml）
    @Value("${spring.data.redis.host:localhost}")
    private String defaultHost;

    @Value("${spring.data.redis.port:6379}")
    private int defaultPort;

    @Value("${spring.data.redis.password:}")
    private String defaultPassword;

    @Value("${spring.data.redis.database:0}")
    private int defaultDatabase;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private final RedisTemplate<String, Object> defaultRedisTemplate;

    // 缓存不同配置的RedisTemplate
    private final Map<String, RedisTemplate<String, Object>> redisTemplateCache = new ConcurrentHashMap<>();


    @Autowired
    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.defaultRedisTemplate = redisTemplate;
        String cacheKey = buildCacheKey(defaultHost, defaultPort, defaultDatabase);
        redisTemplateCache.put(cacheKey, redisTemplate);
    }

    /**
     * 构建缓存Key
     *
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 组合后的缓存Key
     */
    private String buildCacheKey(String host, int port, int dbIndex) {
        return host + ":" + port + "/" + dbIndex;
    }

    /**
     * 获取RedisTemplate（缓存复用）
     *
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return RedisTemplate实例
     */
    private RedisTemplate<String, Object> getRedisTemplate(String host, int port, int dbIndex) {
        String cacheKey = buildCacheKey(host, port, dbIndex);
        return redisTemplateCache.computeIfAbsent(cacheKey, key -> createRedisTemplate(host, port, dbIndex));
    }

    /**
     * 创建RedisTemplate
     *
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 新创建的RedisTemplate实例
     */
    private RedisTemplate<String, Object> createRedisTemplate(String host, int port, int dbIndex) {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        config.setDatabase(dbIndex);
        if (defaultPassword != null && !defaultPassword.isEmpty()) {
            config.setPassword(defaultPassword);
        }

        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(config);
        connectionFactory.afterPropertiesSet();

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(defaultRedisTemplate.getKeySerializer());
        template.setValueSerializer(defaultRedisTemplate.getValueSerializer());
        template.setHashKeySerializer(defaultRedisTemplate.getHashKeySerializer());
        template.setHashValueSerializer(defaultRedisTemplate.getHashValueSerializer());
        template.afterPropertiesSet();
        return template;
    }

    // ========================= String操作 ===========================

    /**
     * 设置字符串值（使用默认配置）
     *
     * @param key   键
     * @param value 值
     */
    public void set(String key, Object value) {
        set(key, value, null, null, null, null);
    }

    /**
     * 设置字符串值（可指定配置）
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间（秒）
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     */
    public void set(String key, Object value, Long timeout, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        redisTemplate.opsForValue().set(key, value);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取字符串值（使用默认配置）
     *
     * @param key 键
     * @return 对应的值
     */
    public Object get(String key) {
        return get(key, null, null, null);
    }

    /**
     * 获取字符串值（可指定配置）
     *
     * @param key     键
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 对应的值
     */
    public Object get(String key, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        return redisTemplate.opsForValue().get(key);
    }

    // ========================= Hash操作 ===========================

    /**
     * 设置哈希值（使用默认配置）
     *
     * @param key     键
     * @param hashKey 哈希键
     * @param value   值
     */
    public void hSet(String key, String hashKey, Object value) {
        hSet(key, hashKey, value, null, null, null, null);
    }

    /**
     * 设置哈希值（可指定配置）
     *
     * @param key     键
     * @param hashKey 哈希键
     * @param value   值
     * @param timeout 过期时间（秒）
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     */
    public void hSet(String key, String hashKey, Object value, Long timeout, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        redisTemplate.opsForHash().put(key, hashKey, value);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取哈希值（使用默认配置）
     *
     * @param key     键
     * @param hashKey 哈希键
     * @return 对应的值
     */
    public Object hGet(String key, String hashKey) {
        return hGet(key, hashKey, null, null, null);
    }

    /**
     * 获取哈希值（可指定配置）
     *
     * @param key     键
     * @param hashKey 哈希键
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 对应的值
     */
    public Object hGet(String key, String hashKey, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 删除哈希表中的一个或多个字段（使用默认配置）
     *
     * @param key      Redis Key（Hash 类型）
     * @param hashKeys 要删除的 Hash 字段名（可变参数）
     */
    public void hDel(String key, String... hashKeys) {
        hDel(key, null, null, null, hashKeys);
    }

    /**
     * 删除哈希表中的一个或多个字段（可指定配置）
     *
     * @param key      Redis Key（Hash 类型）
     * @param host     Redis主机地址
     * @param port     Redis端口
     * @param dbIndex  Redis数据库索引
     * @param hashKeys 要删除的 Hash 字段名（可变参数）
     */
    public void hDel(String key, String host, Integer port, Integer dbIndex, String... hashKeys) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        redisTemplate.opsForHash().delete(key, (Object[]) hashKeys);
    }

    /**
     * 向 Redis Hash 中写入多个字段，并自动设置过期时间（避免 @class 字段）
     *
     * @param redisKey      Redis Key
     * @param expireSeconds 过期时间（秒）
     * @param keyValPairs   成对的 hashKey + value
     */
    public void saveToHash(String redisKey, long expireSeconds, Object... keyValPairs) {
        if (keyValPairs.length % 2 != 0) {
            throw new IllegalArgumentException("参数必须是成对的 hashKey + value");
        }

        // 手动序列化对象为 JSON 字符串，防止写入 @class
        ObjectMapper mapper = new ObjectMapper();

        for (int i = 0; i < keyValPairs.length; i += 2) {
            String hashKey = keyValPairs[i].toString();
            Object value = keyValPairs[i + 1];

            try {
                String jsonValue = mapper.writeValueAsString(value);
                hSet(redisKey, hashKey, jsonValue);
            } catch (JsonProcessingException e) {
                log.warn("JSON 序列化失败: {}", hashKey, e);
            }
        }

        if (expireSeconds > 0) {
            expire(redisKey, expireSeconds);
        }
    }


    /**
     * 获取指定 Hash Key 的所有字段和值，并以 Map<String, String> 形式返回
     *
     * @param key Redis Key（Hash 类型）
     * @return 字段和值组成的 Map
     */
    public Map<String, String> getHashEntriesAsStringMap(String key) {
        return getHashEntriesAsStringMap(key, null, null, null);
    }

    /**
     * 获取指定 Hash Key 的所有字段和值，并以 Map<String, String> 形式返回（可指定 Redis 实例）
     *
     * @param key     Redis Key（Hash 类型）
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 字段和值组成的 Map<String, String>
     */
    public Map<String, String> getHashEntriesAsStringMap(String key, String host, Integer port, Integer dbIndex) {
        Map<String, String> result = new HashMap<>();
        try {
            Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(key);

            if (!entries.isEmpty()) {
                for (Map.Entry<Object, Object> entry : entries.entrySet()) {
                    String hashKey = entry.getKey() != null ? entry.getKey().toString() : "";
                    String hashValue = entry.getValue() != null ? entry.getValue().toString() : "";

                    // 去除 JSON 字符串中的引号
                    if (hashValue.startsWith("\"") && hashValue.endsWith("\"")) {
                        hashValue = hashValue.substring(1, hashValue.length() - 1);
                    }

                    result.put(hashKey, hashValue);
                }
            }
        } catch (Exception e) {
            log.error("从 Redis 读取 Hash 数据失败，key={}", key, e);
        }

        return result;
    }


    // ========================= List操作 ===========================

    /**
     * 左推入列表（使用默认配置）
     *
     * @param key   键
     * @param value 值
     */
    public void lPush(String key, Object value) {
        lPush(key, value, null, null, null, null);
    }

    /**
     * 左推入列表（可指定配置）
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间（秒）
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     */
    public void lPush(String key, Object value, Long timeout, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        redisTemplate.opsForList().leftPush(key, value);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 左弹出列表（使用默认配置）
     *
     * @param key 键
     * @return 弹出的值
     */
    public Object lPop(String key) {
        return lPop(key, null, null, null);
    }

    /**
     * 左弹出列表（可指定配置）
     *
     * @param key     键
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 弹出的值
     */
    public Object lPop(String key, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        return redisTemplate.opsForList().leftPop(key);
    }

    // ========================= Set操作 ===========================

    /**
     * 添加集合元素（使用默认配置）
     *
     * @param key   键
     * @param value 值
     */
    public void sAdd(String key, Object value) {
        sAdd(key, value, null, null, null, null);
    }

    /**
     * 添加集合元素（可指定配置）
     *
     * @param key     键
     * @param value   值
     * @param timeout 过期时间（秒）
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     */
    public void sAdd(String key, Object value, Long timeout, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        redisTemplate.opsForSet().add(key, value);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取集合所有元素（使用默认配置）
     *
     * @param key 键
     * @return 集合所有元素
     */
    public Set<Object> sMembers(String key) {
        return sMembers(key, null, null, null);
    }

    /**
     * 获取集合所有元素（可指定配置）
     *
     * @param key     键
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 集合所有元素
     */
    public Set<Object> sMembers(String key, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        return redisTemplate.opsForSet().members(key);
    }

    // ========================= ZSet操作 ===========================

    /**
     * 添加有序集合元素（使用默认配置）
     *
     * @param key   键
     * @param value 值
     * @param score 分数
     */
    public void zAdd(String key, Object value, double score) {
        zAdd(key, value, score, null, null, null, null);
    }

    /**
     * 添加有序集合元素（可指定配置）
     *
     * @param key     键
     * @param value   值
     * @param score   分数
     * @param timeout 过期时间（秒）
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     */
    public void zAdd(String key, Object value, double score, Long timeout, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        redisTemplate.opsForZSet().add(key, value, score);
        if (timeout != null) {
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取有序集合范围元素（使用默认配置）
     *
     * @param key   键
     * @param start 开始索引
     * @param end   结束索引
     * @return 范围内的元素集合
     */
    public Set<Object> zRange(String key, long start, long end) {
        return zRange(key, start, end, null, null, null);
    }

    /**
     * 获取有序集合范围元素（可指定配置）
     *
     * @param key     键
     * @param start   开始索引
     * @param end     结束索引
     * @param host    Redis主机地址
     * @param port    Redis端口
     * @param dbIndex Redis数据库索引
     * @return 范围内的元素集合
     */
    public Set<Object> zRange(String key, long start, long end, String host, Integer port, Integer dbIndex) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(
                host != null ? host : defaultHost,
                port != null ? port : defaultPort,
                dbIndex != null ? dbIndex : defaultDatabase
        );
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    // ========================= Redis通用命令 ===========================

    /**
     * 查找所有符合给定模式的key
     *
     * @param pattern 匹配模式
     * @return 匹配的key集合
     */
    public Set<String> keys(String pattern) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        return redisTemplate.keys(pattern);
    }

    /**
     * 检查key是否存在
     *
     * @param key 键
     * @return 是否存在
     */
    public boolean exists(String key) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 返回key所储存的值的类型
     *
     * @param key 键
     * @return 值类型
     */
    public String type(String key) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        return Objects.requireNonNull(redisTemplate.type(key)).name();
    }

    /**
     * 删除指定key
     *
     * @param key 键
     */
    public void del(String key) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        redisTemplate.delete(key);
    }

    /**
     * 重命名key
     *
     * @param oldKey 原键名
     * @param newKey 新键名
     */
    public void rename(String oldKey, String newKey) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * 测试连接是否正常
     *
     * @return PONG表示连接正常
     */
    public String ping() {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        return Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().ping();
    }

    /**
     * 设置key的过期时间（秒）
     *
     * @param key     键
     * @param seconds 过期时间（秒）
     */
    public void expire(String key, long seconds) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
    }

    /**
     * 返回给定key的剩余生存时间(TTL)
     *
     * @param key 键
     * @return 剩余生存时间（秒），-1表示没有设置过期时间，-2表示key不存在
     */
    public Long ttl(String key) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate(defaultHost, defaultPort, defaultDatabase);
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
}